二分搜索树基础
二叉树

特征:
- 具有唯一的根结点
- 有左孩子和右孩子
- 每个节点最多有两个孩子

叶子节点 :左右孩子为空


二分搜索树 


Tips:存储的元素必须有可比较性
二分搜索树定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class BST<E extends Comparable<E>> { private class Node { public E e; public Node left, right;
public Node(E e) { this.e = e; left = null; right = null; } }
private Node root; private int size;
public BST() { root = null; size = 0; } }
|
二分搜索树添加元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public class BST<E extends Comparable<E>> { private class Node { public E e; public Node left, right;
public Node(E e) { this.e = e; left = null; right = null; } }
private Node root; private int size;
public BST() { root = null; size = 0; }
public int size() { return size; }
public boolean isEmpty() { return size == 0; }
public void add(E e) { if (root == null) { root = new Node(e); size++; } else { add(root, e); } }
private void add(Node node, E e) { if (e.equals(node.e)) { return; } else if (e.compareTo(node.e) < 0 && node.left == null) { node.left = new Node(e); size++; return; } else if (e.compareTo(node.e) > 0 && node.right == null) { node.right = new Node(e); size++; return; } if (e.compareTo(node.e) < 0) { add(node.left, e); } else add(node.right, e); } }
|
改进二分搜索树 (深入理解终止条件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| private Node add(Node node, E e) { if (node == null) { size++; return new Node(e); } if (e.compareTo(node.e) < 0) { node.left = add(node.left, e); } else if (e.compareTo(node.e) > 0) { node.right = add(node.right, e); } return node;
}
|
二分搜索树查询元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public boolean contains(E e) { return contains(root, e); }
private boolean contains(Node node, E e) { if (node == null) { return false; } if (e.compareTo(node.e) == 0) { return true; } else if (e.compareTo(node.e) < 0) { return contains(node.left, e); } else return contains(node.right, e); }
|
二分搜索树前序遍历

1 2 3 4 5 6 7 8 9 10 11 12
| public void preOrder() { preOrder(root); } private void proOrder(Node node) { if (node == null) return; System.out.println(node.e); preOrder(node.left); preOrder(node.right); }
|
二分搜索树 中序遍历

1 2 3 4 5 6 7 8 9
| private void inOrder(Node node) { if (node == null) { return; } inOrder(node.left); System.out.println(node.e); inOrder(node.right); }
|
二分搜索树 后序遍历
1 2 3 4 5 6 7 8 9 10 11 12
| private void postOrder() { postOrder(root); }
private void postOrder(Node node) { if (node == null) return; postOrder(node.left); postOrder(node.right); System.out.println(node.e); }
|
深入理解二分搜索树的遍历






二分搜索树的非递归前序遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void preOrderNR() { Stack<Node> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { Node cur = stack.pop(); System.out.println(cur.e); if (cur.right != null) { stack.push(cur.right); } if (cur.left != null) { stack.push(cur.left); }
}
|
二分搜索树的层序遍历


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| TODO 代码层序遍历
public void levelOrder() { Queue<Node> q = new LinkedList<>(); q.add(root); while (!q.isEmpty()) { Node cur = q.remove(); System.out.println(cur.e); if (cur.left != null) q.add(cur.left); if (cur.right != null) q.add(cur.right); }
}
|
查找二分搜索树的最小值和最大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
public E minium() { if (size == 0) throw new IllegalArgumentException("BST size=0"); return minium(root).e; }
private Node minum(Node node) { if (node.left == null) return node; return minium(node.left); }
public E minium() { if (size == 0) throw new IllegalArgumentException("BST size=0"); return minium(root).e; }
private Node minum(Node node) { if (node.right == null) return node; return minium(node.right); }
|
删除二分搜索树的最 大 小值

二分搜索树删除任意节点





在5.2中完成了树的遍历,这一节中将对如何从二叉搜索树中删除最大元素和最小元素做介绍: 我们要想删除二分搜索树的最小值和最大值,就需要先找到二分搜索树的最小值和最大值,其实也还是很容易的,因为根据二叉搜索树的特点,它的左子树一定比当前节点要小,所以二叉搜索树的最小值一定是左子树一直往下走,一直走到底。同样在二叉搜索树中,右子树节点值,一定比当前节点要大,所以右子树一直往下走,就一定是最大值。
注意向左走一直到走不动并不是一定要达到叶子节点,只用达到走不动为止,看下图的例子:

向左走到16就走不动了,但是16下面还有元素。
一、查询操作
1.1 查询二分搜索树的最小节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public E minimum() { if (size == 0) { throw new IllegalArgumentException("BST is empty"); }
Node ninNode = minimum(root); return ninNode.e; }
private Node minimum(Node node) { if (node.left == null) { return node; }
return minimum(node.left); }
|
1.2 查询二分搜索树的最大节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public E maxmum() { if (size == 0) throw new IllegalArgumentException("BST is empty"); Node maxNode = maxmum(root);
return maxNode.e; }
private Node maxmum(Node node) { if (node.right == null) { return node; }
return maxmum(node.right); }
|
二、删除操作
删除最小值的思路: 1)如果要删除的节点是叶子节点,那么直接删除 2)如果要删除的节点下面有右子树,那么只用将其下的右子树整体上移成为上一个节点的左子树即可

当删除22这个节点后,把33这个节点及其以下的子树变成41节点的左子树即可。
2.1 删除最小值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public E removeMin() { E ret = minimum(); root = removeMin(root);
return ret; }
private Node removeMin(Node node) {
if (node.left == null) { Node rightNode = node.right; node.right = null; size--; return rightNode; }
node.left = removeMin(node.left);
return node; }
|
2.2 删除最大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public E removeMax() { E ret = maxmum(); root = removeMax(root); return ret; }
private Node removeMax(Node node) { if (node.right == null) { Node leftNode = node.left; node.left = null; size--; return leftNode; }
node.right = removeMax(node.right); return node;
}
|
一.删除思路分析
在删除二叉搜索树的任意元素时,会有三种情况:
1.1 删除只有左孩子的节点
节点删除之后,将左孩子所在的二叉树取代其位置;连在原来节点父亲元素右节点的位置,比如在图中需要删除58
这个节点。

删除58
这个节点后,如下图所示:

1.2 删除只有右孩子的节点:
节点删除之后,将右孩子所在的二叉树取代其位置;连在原来节点的位置,比如在下图中需要删除58
这个节点。

删除58这个节点后,如下图所示:

这里需要说明说一下,以上两种情况其实包含了叶子节点情况的,我们可以把叶子节点理解成只有左孩子的节点,也可以把它理解为只有右孩子的节点,只不过左孩子、右孩子为null
。
1.3 删除包含左右孩子的节点
如下图,二叉搜索树包含有左右孩子,假设现需要删除58
这个节点。

针对该种情况,分析如下: 我们把58
这个节点记为d
节点(包含有左子树与右子树),如下图所示:

针对这种节点删除情况需要把左子树与右子树融合起来,融合方法: 从d
这节点的左孩子与右孩子中找一个比d
节点还要大的节点取代d
节点,根据二叉搜索树的性质可知(左边节点<当前节点<右边节点),这个需要被找的节点存在于d
节点的右孩子节点中。
寻找规则: 寻找需要被删除节点58(d
)的后继的所有元素中,离 58 最近的且比 58 大的节点,在本例中为59
这个节点【即右子树中的最小值】,记为s
,如下图所示:

删除步骤:
(1)从d
的右子树中删除最小值,将删除最小值s
后的d
的右子树, 变为d
后继节点s
的右孩子,如下图所示:

(2)将d
节点(58
节点)的左子树,变为后继节点s
(59
节点)的左子树,如下图所示:

(3)将后继节点s
(59
节点)连接到d
节点(58
节点)父亲节点的右边,删除d
节点(58
节点)后,后继s
节点(59
节点)成为新的根,如下图所示:

二、编码实现删除二叉搜索树的任意元素
根据上述的分析,在此基础上进行编码,删除代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public void remove(E e) { root = remove(root, e); }
private Node remove(Node node, E e) { if (node == null) return null;
if (e.compareTo(node.e) < 0) { node.left = remove(node.left, e); return node; } if (e.compareTo(node.e) > 0) { node.right = remove(node.right, e); return node; } else {
if (node.left == null) { Node rightNode = node.right; node.right = null; size--; return rightNode; }
if (node.right == null) { Node leftNode = node.left; node.left = null; size--; return leftNode; }
Node successor = minimum(node.right); successor.right = removeMin(node.right); successor.left = node.left; node.left = node.right = null;
return successor; } }
|
对于上述代码中的minimum函数,在5.3节中已经实现,此处同样也把代码列出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public E minimum() { if (size == 0) { throw new IllegalArgumentException("BST is empty"); }
Node ninNode = minimum(root); return ninNode.e; }
private Node minimum(Node node) { if (node.left == null) { return node; }
return minimum(node.left); }
|
一.删除思路分析
在删除二叉搜索树的任意元素时,会有三种情况:
1.1 删除只有左孩子的节点
节点删除之后,将左孩子所在的二叉树取代其位置;连在原来节点父亲元素右节点的位置,比如在图中需要删除58
这个节点。

删除58
这个节点后,如下图所示:

1.2 删除只有右孩子的节点:
节点删除之后,将右孩子所在的二叉树取代其位置;连在原来节点的位置,比如在下图中需要删除58
这个节点。

删除58这个节点后,如下图所示:

这里需要说明说一下,以上两种情况其实包含了叶子节点情况的,我们可以把叶子节点理解成只有左孩子的节点,也可以把它理解为只有右孩子的节点,只不过左孩子、右孩子为null
。
1.3 删除包含左右孩子的节点
如下图,二叉搜索树包含有左右孩子,假设现需要删除58
这个节点。

针对该种情况,分析如下: 我们把58
这个节点记为d
节点(包含有左子树与右子树),如下图所示:

针对这种节点删除情况需要把左子树与右子树融合起来,融合方法: 从d
这节点的左孩子与右孩子中找一个比d
节点还要大的节点取代d
节点,根据二叉搜索树的性质可知(左边节点<当前节点<右边节点),这个需要被找的节点存在于d
节点的右孩子节点中。
寻找规则: 寻找需要被删除节点58(d
)的后继的所有元素中,离 58 最近的且比 58 大的节点,在本例中为59
这个节点【即右子树中的最小值】,记为s
,如下图所示:

删除步骤:
(1)从d
的右子树中删除最小值,将删除最小值s
后的d
的右子树, 变为d
后继节点s
的右孩子,如下图所示:

(2)将d
节点(58
节点)的左子树,变为后继节点s
(59
节点)的左子树,如下图所示:

(3)将后继节点s
(59
节点)连接到d
节点(58
节点)父亲节点的右边,删除d
节点(58
节点)后,后继s
节点(59
节点)成为新的根,如下图所示:

二、编码实现二叉搜索树的任意元素
根据上述的分析,在此基础上进行编码,删除代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public void remove(E e) { root = remove(root, e); }
private Node remove(Node node, E e) { if (node == null) return null;
if (e.compareTo(node.e) < 0) { node.left = remove(node.left, e); return node; } if (e.compareTo(node.e) > 0) { node.right = remove(node.right, e); return node; } else {
if (node.left == null) { Node rightNode = node.right; node.right = null; size--; return rightNode; }
if (node.right == null) { Node leftNode = node.left; node.left = null; size--; return leftNode; }
Node successor = minimum(node.right); successor.right = removeMin(node.right); successor.left = node.left; node.left = node.right = null;
return successor; } }
|
对于上述代码中的minimum函数,在5.3节中已经实现,此处同样也把代码列出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public E minimum() { if (size == 0) { throw new IllegalArgumentException("BST is empty"); }
Node ninNode = minimum(root); return ninNode.e; }
private Node minimum(Node node) { if (node.left == null) { return node; }
return minimum(node.left); }
|